class_name LaserBeam
extends Node2D

@export var line_width: float = 4.0
@export var show_laser_time: float = 0.1
@export var max_length: float = 200
@export var lase_grow_speed: float = 3000
var is_casting: bool = false

@onready var line_2d: Line2D = $Line2D
@onready var ray_cast_2d: RayCast2D = $RayCast2D
@onready var laser_gun_casting_effect: GPUParticles2D = $LaserGunCastingEffect
@onready var laser_gun_collision_effect: GPUParticles2D = $LaserGunCollisionEffect

@onready var light_parent: Node2D = $Light
@onready var laser_light: PointLight2D = $Light/LaserLight
@export var laser_light_width: float = 8

var cooldown_dict: Dictionary[Node2D, float] = {}
@export var hit_cooldown_time: float = 0.5

var sender: Node2D
var attack: float = 0
var critical_rate: float = 0
var critical_bonus: float = 0
var knockback: float = 0
var hit_cam_shake_strength: float = 0
var hit_cam_shake_duration: float = 0

var level: Level

@export var show_laser_tip: bool = false
@onready var laser_tip: Line2D = $LaserParent/LaserTip
@onready var laser_tip_dot: Node2D = $LaserParent/LaserTipDot

@onready var laser_fire_audio: AudioStreamPlayer2D = $AudioStreamPlayer2D


func _ready() -> void:
	ray_cast_2d.target_position.x = max_length
	line_2d.width = 0
	
	laser_tip.visible = show_laser_tip
	laser_tip_dot.visible = false


func _physics_process(delta: float) -> void:
	if show_laser_tip:
		process_view_laser_tip(delta)
	process_view_laser_line(delta)
	process_clear_cooldown(delta)
	if is_casting:
		process_hit()
		

func process_view_laser_tip(delta):
	if ray_cast_2d.is_colliding():
		var target_length = to_local(ray_cast_2d.get_collision_point()).x
		laser_tip.points[1].x = target_length
		laser_tip_dot.visible = true
		laser_tip_dot.position.x = target_length
	else:
		laser_tip.points[1].x = max_length
		laser_tip_dot.visible = false
	

func process_view_laser_line(delta: float):
	if is_casting:
		var target_length = max_length
		if ray_cast_2d.is_colliding():
			target_length = to_local(ray_cast_2d.get_collision_point()).x
			laser_gun_collision_effect.position.x = target_length + 4
			laser_gun_collision_effect.emitting = true
		else:
			laser_gun_collision_effect.emitting = false
			
		line_2d.points[1].x = move_toward(line_2d.points[1].x, target_length, lase_grow_speed * delta)
		laser_light.scale.x = 1.0 / 16.0 * line_2d.points[1].x
		light_parent.position.x = line_2d.points[1].x / 2.0
	else:
		laser_gun_collision_effect.emitting = false
		

func set_is_casting(new_value: bool):
	if is_casting == new_value: return
	
	is_casting = new_value
	if new_value:
		show_laser()
		laser_fire_audio.play()
	else:
		hide_laser()
		laser_fire_audio.stop()
	
	
func show_laser():
	var tween = create_tween()
	tween.tween_property(line_2d, "width", line_width, show_laser_time) \
	.set_trans(Tween.TRANS_CUBIC) \
	.set_ease(Tween.EASE_OUT).from_current()
	
	tween = create_tween()
	tween.tween_property(laser_light, "scale:y", 1.0 / 16.0 * laser_light_width, show_laser_time) \
	.set_trans(Tween.TRANS_CUBIC) \
	.set_ease(Tween.EASE_OUT).from_current()
	
	laser_gun_casting_effect.emitting = true
	

func hide_laser():
	var tween = create_tween()
	tween.tween_property(line_2d, "width", 0, show_laser_time) \
	.set_trans(Tween.TRANS_CUBIC) \
	.set_ease(Tween.EASE_IN).from_current()
	
	tween = create_tween()
	tween.tween_property(laser_light, "scale:y", 0, show_laser_time) \
	.set_trans(Tween.TRANS_CUBIC) \
	.set_ease(Tween.EASE_IN).from_current()
	
	laser_gun_casting_effect.emitting = false
	

func process_hit():
	if not ray_cast_2d.is_colliding(): return
	
	var collider = ray_cast_2d.get_collider()
	if not collider is Area2D: return
	
	var source = collider.source
	if not source.has_method("take_hit"): return
	
	if cooldown_dict.has(source): return
	
	cooldown_dict[source] = hit_cooldown_time
	source.take_hit(get_hit_data())
	
	if hit_cam_shake_strength:
		if not level:
			level = get_tree().current_scene as Level
		level.camera.start_shake(hit_cam_shake_strength, hit_cam_shake_duration)
	
	
func get_hit_data():
	var is_critical = randf() <= critical_rate
	var critical_attack = attack * (1.0 + critical_bonus)
	
	var hit_data = HitData.new()
	hit_data.is_critical = is_critical
	hit_data.attack = critical_attack if is_critical else attack
	hit_data.knockback = knockback
	
	return hit_data
	

func process_clear_cooldown(delta: float):
	var to_remove := []
	for key in cooldown_dict.keys():
		if not is_instance_valid(key):
			to_remove.append(key)
			continue
			
		cooldown_dict[key] -= delta
		if cooldown_dict[key] <= 0:
			to_remove.append(key)
	
	for key in to_remove:
		cooldown_dict.erase(key)


func set_is_enemy(is_enemy: bool):
	if is_enemy:
		ray_cast_2d.set_collision_mask_value(7, true)
		ray_cast_2d.set_collision_mask_value(6, false)
	else:
		ray_cast_2d.set_collision_mask_value(7, false)
		ray_cast_2d.set_collision_mask_value(6, true)
